package com.bite.friend.Service.ServiceImpl;

import Constants.JwtConstants;
import Constants.RedisConstant;
import Utils.JwtUtil;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.bite.common.security.domain.LoginUser;
import com.bite.common.security.enums.UserType;
import com.bite.friend.DTO.UserDTO;
import com.bite.friend.DTO.UserHeadImageDTO;
import com.bite.friend.Mapper.UserMapper;
import com.bite.friend.Service.UserService;
import com.bite.friend.DTO.UserDetailDTO;
import com.bite.friend.VO.UserInfoVO;
import com.bite.friend.VO.UserVO;
import com.bite.friend.doamin.User;
import com.bite.message.Service.AliSmsService;
import domain.MyThreadLocal;
import domain.R;
import enums.ResultCode;
import io.jsonwebtoken.Claims;
import lombok.extern.slf4j.Slf4j;
import org.dromara.x.file.storage.core.FileInfo;
import org.dromara.x.file.storage.core.FileStorageService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

@Service
@Slf4j
public class UserServiceImpl implements UserService {

    @Autowired
    private UserMapper userMapper;

    @Autowired
    private AliSmsService aliSmsService;

    @Autowired
    private StringRedisTemplate redis;

    @Value("${jwt.secret}")
    private String secret;

    @Value("${sms.is-send:false}")
    private boolean isSend;  //开关打开：true  开关关闭false

    @Autowired
    private FileStorageService fileStorageService;//注入实列



    @Override
    public R<Void> send(UserDTO userDTO) {
        //先判断手机格式是否合理
        if (!checkPhone(userDTO.getPhone())){
            throw new RuntimeException("手机格式不正确");
        }
        //接着生成随机验证码
        String code = isSend ? RandomUtil.randomNumbers(6) : "123456";

        String codeKey = setCodeKey(userDTO.getPhone());

        String countKey = setCountKey(userDTO.getPhone());

        String s = redis.opsForValue().get(countKey);
        //每一个账号每一天最多只能发送50个验证码
        if(s != null && Integer.parseInt(s) >= RedisConstant.CODE_COUNT){
            throw new RuntimeException("访问已经达到了限制");
        }


        //在一分钟之内不能重复发送验证码
        //先判断是否是第一次发送验证码
        Long expire = redis.getExpire(codeKey,TimeUnit.SECONDS);
        //说明不是第一次发送验证码,且在一分钟之内再次发送验证码
        if (expire != null && (RedisConstant.CODE_TTL * 60 - expire < 60)){
            throw new RuntimeException("一分钟之内不能重复发送验证码");
        }


        //将生成的验证码放入redis中
        redis.opsForValue().set(codeKey, code,RedisConstant.CODE_TTL, TimeUnit.MINUTES);

        boolean res = true;
        //发送短信信息
        if (isSend){
            res = aliSmsService.sendMobileCode(userDTO.getPhone(), code);
        }


        //将发送次数放入redis中
        redis.opsForValue().increment(countKey,RedisConstant.STEP);

        //接着设置countKey的过期时间
        if (s == null){
            redis.expire(countKey,RedisConstant.COUNT_TTL,TimeUnit.DAYS);
        }

        if (res){
            return R.success();
        }
        return R.failed();
    }

    @Override
    public R<String> login(UserDTO userDTO) {
        //根据手机号查询验证码
        String code = redis.opsForValue().get(setCodeKey(userDTO.getPhone()));
        if (StrUtil.isEmpty(code)){
            //验证失败
            return R.failed(ResultCode.FAILED_CODE_ERROR);
        }
        //验证码比对是否正确
        if (!code.equals(userDTO.getCode())){
            //比对失败
            return R.failed(ResultCode.FAILED_CODE_INCORRECT);
        }
        //比对成功之后，删除验证码
        redis.delete(setCodeKey(userDTO.getPhone()));


        //尝试根据用户电话号码获取用户信息
        User user = userMapper.selectOne(new LambdaQueryWrapper<User>()
                .eq(User::getPhone, userDTO.getPhone()));
        if (user == null){
            //说明该用户是新用户，需要先注册，再进行登录
            user= new User();
            user.setStatus(1);
            user.setCreateBy(1L);
            user.setPhone(userDTO.getPhone());
            user.setCreateTime(LocalDateTime.now());
            userMapper.insert(user);
        }

        //登录成功，返回token
        String jwt = getToken(user);
        return R.success(jwt);
    }

    @Override
    public R<Void> logOut(String token) {
        //判断token是否存在
        if (StrUtil.isEmpty(token)){
            throw new RuntimeException("token不能为空");
        }
        //接着对token进行解析
        Claims claims;
        try{
            claims = JwtUtil.parseJWT(token, secret);
            if (claims == null){
                throw new RuntimeException("token解析失败");
            }
        }catch (Exception e){
            throw new RuntimeException("token解析失败");
        }
        String userKey = claims.get(JwtConstants.USER_KEY).toString();
        String loginUserStr = redis.opsForValue().get(userKey);
        if (StrUtil.isEmpty(loginUserStr)){
            throw new RuntimeException("token已过期");
        }
        LoginUser loginUser = JSONUtil.toBean(loginUserStr, LoginUser.class);
        log.info("用户信息为loginUser:{}",loginUser);
        //接着将用户信息从redis中删除
        Boolean delete = redis.delete(userKey);
        if (Boolean.TRUE.equals(delete)){
            return R.success();
        }
        return R.failed();
    }

    @Override
    public R<UserInfoVO> getUserInfo() {
        String userId = MyThreadLocal.get();
        if (StrUtil.isEmpty(userId)){
            throw new RuntimeException("用户未登录");
        }
        //根据用户ID查询信息
        User user = userMapper.selectById(userId);
        UserInfoVO userInfoVO = BeanUtil.copyProperties(user, UserInfoVO.class);
        return R.success(userInfoVO);
    }

    @Override
    public R<UserVO> personal() {
        //首先拿到用户id
        String userId = MyThreadLocal.get();
        //判断redis中是否存在该用户信息
        String key = RedisConstant.USER_CENTER + userId;
        String userDetail = redis.opsForValue().get(key);
        if (StrUtil.isEmpty(userDetail)){
            //redis中为空，则从数据库中获取数据
            return flashUserDetail(Long.valueOf(userId));
        }
        UserVO userVO = JSONUtil.toBean(userDetail, UserVO.class);
        return R.success(userVO);
    }

    @Override
    public R<Void> edit(UserDetailDTO userDetailDTO) {
        //先根据用户id来获取用户的信息
        Long userId = userDetailDTO.getUserId();
        if (userId == null){
            throw new RuntimeException("用户id不能为空");
        }
        User user = userMapper.selectById(userId);
        if (user == null){
            throw new RuntimeException("用户不存在");
        }
        BeanUtil.copyProperties(userDetailDTO,user);
        int i = userMapper.updateById(user);
        if (i > 0){
            //重新刷新缓存
            redis.opsForValue().set(RedisConstant.USER_CENTER+userId, JSONUtil.toJsonStr(user)
                    ,RedisConstant.USER_INFO_TTL, TimeUnit.MINUTES);
            return R.success();
        }
        return R.failed();
    }

    @Override
    public R<String> headshot(MultipartFile file) {
        try {
            // 指定oss保存文件路径
            String objectName = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy/MM/dd")) + "/";
            // 上传图片，成功返回文件信息
            FileInfo fileInfo = fileStorageService.of(file).setPath(objectName).upload();
            // 设置返回结果
            return R.success(fileInfo.getUrl());
        } catch (Exception e) {
            throw new RuntimeException("上传失败");
        }
    }

    @Override
    public R<Void> updateHeadImage(UserHeadImageDTO userHeadImageDTO) {
        //先获取用户Id
        String userId = MyThreadLocal.get();
        User user = userMapper.selectById(userId);
        if (user == null){
            return R.failed(ResultCode.FAILED_USER_NOT_EXISTS);
        }
        user.setHeadImage(userHeadImageDTO.getHeadImage());
        int i = userMapper.updateById(user);
        if (i > 0){
            //还得重新刷新数据
            UserVO userVO = BeanUtil.copyProperties(user, UserVO.class);
            redis.opsForValue().set(RedisConstant.USER_CENTER+userId, JSONUtil.toJsonStr(userVO),
                    RedisConstant.USER_INFO_TTL, TimeUnit.MINUTES);
            return R.success();
        }
        return R.failed();
    }

    private R<UserVO> flashUserDetail(Long userId) {
        //首先从数据库中获取数据
        User user = userMapper.selectById(userId);
        if (user == null){
            return R.failed(ResultCode.FAILED_USER_NOT_EXISTS);
        }
        //如果数据存在，则将数据存放到redis中
        UserVO userVO = BeanUtil.copyProperties(user, UserVO.class);
        redis.opsForValue().set(RedisConstant.USER_CENTER+userId, JSONUtil.toJsonStr(userVO),
                RedisConstant.USER_INFO_TTL, TimeUnit.MINUTES);
        return R.success(userVO);
    }

    private String getToken(User user){
        Map<String,Object> map = new HashMap<>();

        String userKey = UUID.randomUUID().toString();

        map.put(JwtConstants.USER_ID,user.getUserId());

        map.put(JwtConstants.USER_KEY,userKey);

        //生成token
        String token = JwtUtil.createJWT(map, secret);

        //将重要信息放置到redis中

        LoginUser loginUser = new LoginUser(UserType.USER_TYPE.getIdentity());
        //将对象格式转换为字符串格式

        String loginUserStr = JSONUtil.toJsonStr(loginUser);
        redis.opsForValue().set(userKey,loginUserStr,JwtConstants.EXPIRATION, TimeUnit.MINUTES);

        return token;
    }

    private String setCountKey(String phone) {
        return RedisConstant.USER_COUNT_KEY + phone;
    }

    private String setCodeKey(String phone){
        return RedisConstant.USER_CODE_KEY + phone;
    }


    public static boolean checkPhone(String phone) {
        Pattern regex = Pattern.compile("^1[2|3|4|5|6|7|8|9][0-9]\\d{8}$");
        Matcher m = regex.matcher(phone);
        return m.matches();
    }
}
